/* VectorValue.cs --
   Copyright (C) 2008  Pacific Biosciences, Inc. */


using System;
using System.Collections.Generic;

namespace SexpSharp.RPC
{
	public class VectorValue : IValue
	{
		internal Array _value;

		public VectorValue(Expression e)
		{
			List list = e as List;
			if (list == null)
				throw new ArgumentException("must be a list", "e");
			if (list.Count != 3)
				throw new ArgumentException("list must have at three atoms");
			Atom name = list.First as Atom;
			if (name == null)
				throw new ArgumentException("first element must be an atom");
			if (!name.ToString().Equals(Values.VectorName))
				throw new ArgumentException("first element must be `" + Values.VectorName + "'");

			Atom size = list.Second as Atom;
			if (size == null)
				throw new ArgumentException("second element must be an atom");
			Atom val = list.Rest.Second as Atom;
			if (val == null)
				throw new ArgumentException("third element must be an atom");

			int esize = size.ToInt32(null);
			byte[] bytes = val.Bytes;
			switch (esize)
			{
			case 2:
				if ((bytes.Length % 2) != 0)
					throw new ArgumentException("vector value is not a multiple of 2 bytes long");
				_value = new ushort[bytes.Length / 2];
				for (int i = 0; i < _value.Length; i++)
				{
					ushort s = (ushort) (((bytes[i*2] & 0xFFU) << 8) | (bytes[i*2+1] & 0xFFU));
					_value.SetValue(s, i);
				}
				break;

			case 4:
				if ((bytes.Length % 4) != 0)
					throw new ArgumentException("vector value is not a multiple of 4 bytes long");
				_value = new uint[bytes.Length / 4];
				for (int i = 0; i < _value.Length; i++)
				{
					uint u = (((bytes[i*4] & 0xFFU) << 24) | ((bytes[i*4+1] & 0xFFU) << 16)
							  | ((bytes[i*4+2] & 0xFFU) << 8) | (bytes[i*4+3] & 0xFFU));
					_value.SetValue(u, i);
				}
				break;

			case 8:
				if ((bytes.Length % 8) != 0)
					throw new ArgumentException("vector value is not a multiple of 8 bytes long");
				_value = new ulong[bytes.Length / 8];
				for (int i = 0; i < _value.Length; i++)
				{
					ulong l = (((bytes[i*8] & 0xFFUL) << 56) | ((bytes[i*8+1] & 0xFFUL) << 48)
							   | ((bytes[i*8+2] & 0xFFUL) << 40) | ((bytes[i*8+3] & 0xFFUL) << 32)
							   | ((bytes[i*8+4] & 0xFFUL) << 24) | ((bytes[i*8+5] & 0xFFUL) << 16)
							   | ((bytes[i*8+6] & 0xFFUL) <<  8) | (bytes[i*8+7] & 0xFFUL));
					_value.SetValue(l, i);
				}
				break;

			default:
				throw new ArgumentException("invalid vector element size: " + esize);
			}
		}

		public VectorValue(ushort[] array)
		{
			_value = array;
		}

		public VectorValue(uint[] array)
		{
			_value = array;
		}

		public VectorValue(ulong[] array)
		{
			_value = array;
		}

		public VectorValue(Array array)
		{
			if (!(array is short[] || array is ushort[]
				  || array is int[] || array is uint[]
				  || array is long[] || array is ulong[]))
				throw new ArgumentException("incompatible array type: " + array.GetType());
			_value = array;
		}

		public Array Value
		{
			get { return _value; }
			set {
				if (!(_value is short[] || _value is ushort[]
					  || _value is int[] || _value is uint[]
					  || _value is long[] || _value is ulong[]))
					throw new ArgumentException("incompatible array type");
				_value = value;
			}
		}

		public Expression ToExpression()
		{
			List l = new List();
			l.Add(new Atom(Values.VectorName));
			if (_value is short[] || _value is ushort[])
			{
				l.Add(new Atom(2));
				byte[] b = new byte[_value.Length * 2];
				for (int i = 0; i < _value.Length; i++)
				{
					ushort s = ((IConvertible) _value.GetValue(i)).ToUInt16(null);
					b[i*2] = (byte) (s >> 8);
					b[i*2+1] = (byte) s;
				}
				l.Add(new Atom(b));
			}
			else if (_value is int[] || _value is uint[])
			{
				l.Add(new Atom(4));
				byte[] b = new byte[_value.Length * 4];
				for (int i = 0; i < _value.Length; i++)
				{
					uint u = ((IConvertible) _value.GetValue(i)).ToUInt32(null);
					b[i*4  ] = (byte) (u >> 24);
					b[i*4+1] = (byte) (u >> 16);
					b[i*4+2] = (byte) (u >>  8);
					b[i*4+3] = (byte) u;
				}
				l.Add(new Atom(b));
			}
			else
			{
				l.Add(new Atom(8));
				byte[] b = new byte[_value.Length * 8];
				for (int i = 0; i < _value.Length; i++)
				{
					ulong u = ((IConvertible) _value.GetValue(i)).ToUInt64(null);
					b[i*8  ] = (byte) (u >> 56);
					b[i*8+1] = (byte) (u >> 48);
					b[i*8+2] = (byte) (u >> 40);
					b[i*8+3] = (byte) (u >> 32);
					b[i*8+4] = (byte) (u >> 24);
					b[i*8+5] = (byte) (u >> 16);
					b[i*8+6] = (byte) (u >>  8);
					b[i*8+7] = (byte) u;
				}
				l.Add(new Atom(b));
			}
			return l;
		}

		public bool Equals(IValue that)
		{
			if (that is VectorValue)
			{
				Array val = ((VectorValue) that)._value;
				if (val.Length != _value.Length)
					return false;
				if (!val.GetType().Equals(_value.GetType()))
					return false;
				for (int i = 0; i < _value.Length; i++)
					if (!_value.GetValue(i).Equals(val.GetValue(i)))
						return false;
				return true;
			}
			return false;
		}

		public override bool Equals(object o)
		{
			if (o is IValue)
				return Equals((IValue) o);
			return false;
		}

		public override int GetHashCode()
		{
			int code = 0;
			foreach (object o in _value)
				code ^= o.GetHashCode();
			return code;
		}
	}
}